#include <iostream>
#include <ctype.h>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <utility>
#include <fstream>
#include <sstream>
#include <memory>

using std::cout;
using std::cin;
using std::endl;
using std::cerr;
using std::string;
using std::map;
using std::multiset;
using std::vector;
using std::ostream;
using std::ifstream;
using std::ofstream;
using std::istringstream;
using std::shared_ptr;

/*
使用提供的文件内容，然后查找单词 "element"。输出的前几行为：
---------------------------------------------
element occurs 125 times.
(line 62) element with a given key.
(line 64) second element with the same key.
(line 153) element |==| operator.
(line 250) the element type.
(line 398) corresponding element.
---------------------------------------------

1、可以查询多次，每次都手动输入要查询的词
2、将输出的内容全部打包到一个类
3、为了实现2，则需要将一些内容传入这个类。如果传入复制文本等信息本身，则工作量太大
   那么可以传入指针，这时就联想到了智能指针
*/

using line_no = vector<string>::size_type;  //就是size_t，有点多此一举

class QueryResult{
    friend ostream &operator<<(ostream &os, const QueryResult & qr);
public:
    QueryResult(const string s,
                shared_ptr<multiset<line_no>> l,
                shared_ptr<vector<string>> f
               )
        : _sought(s), _lines(l), _file(f)
    {

    }

private:
    string _sought;
    shared_ptr<multiset<line_no>> _lines;
    //lines是指向记录某个单词出现的所有行号的set的智能指针
    //不需要把整个 map<string, shared_ptr<set<line_no>>> _wordlines 拿进来

    shared_ptr<vector<string>> _file;
    //也不需要把整个复制文本传进来，只用拿它的指针即可
};


class TextQuery
{
public:

    TextQuery(ifstream &ifs)
    : _file(new vector<string>)
    {
        string line;
        while(getline(ifs, line)){
            _file->push_back(line); //复制整个文本

            int current_line_no = _file->size()-1;
            istringstream iss(line);
            string word;
            while(iss >> word){
                string real_word = dealword(word);
                if(real_word != string()){
                    auto &lines = _wordlines[real_word];
                    //lines是指向记录某个单词出现的所有行号的set的智能指针
                    //加&，表明操作的确实是同一个set
                    //
                    //如果 real_word 不存在于 _wordlines 中，将会自动插入一个新的键值对，
                    //键为 real_word，值为一个默认构造的 shared_ptr<set<line_no>>
                    if(!lines)
                        lines.reset(new multiset<line_no>);
                        //如果 lines 指向的 shared_ptr 智能指针为空（即指向 nullptr），
                        //则创建一个新的 set<line_no> 对象，并将 lines 指向该新对象；
                        //如果 lines 已经指向一个对象，则不执行任何操作。
                    lines->insert(current_line_no);
                }
            }
        }
        ifs.close();
    }

    string dealword(const string &word){
        string processed_word;
        for(size_t idx=0;idx!=word.size();++idx){
            if(isalpha(word[idx])){
                processed_word += word[idx];
                //这里不能 return string();
                //因为这样会漏掉 diners, 这种在句尾的单词
                //应该把标点符号去掉，统计这个单词
                //但是这也存在一个问题，即会把(xiaoyage222)这种作为xiaoyag统计1次
            }
        }
        return processed_word;
    }

    QueryResult query(const string &s) {
        static shared_ptr<multiset<line_no>> nodata(new multiset<line_no>);
        // nodata 是一个静态变量，意味着它在程序生命周期内只会被初始化一次，
        // 即使在函数内部也是如此。静态变量在程序启动时被初始化，直到程序结束才被销毁。

        //auto &loc = _wordlines[s];
        //使用find而不是下标访问运算符，避免将单词添加到_wordlines中
        //使用下标访问运算符 [] 来访问容器中不存在的键时，会导致该键被添加到容器中
        //这不同于上面，因为上面的单词是肯定存在的，这里由用户输入的查询单词不一定存在
        auto loc = _wordlines.find(s);
        
        if(loc == _wordlines.end())
            return QueryResult(s, nodata, _file);
        else
            return QueryResult(s, loc->second, _file);
    }

private:
    shared_ptr<vector<string>> _file;
    map<string, shared_ptr<multiset<line_no>>> _wordlines;
};

string make_plural(size_t ctr, const string &word, const string &ending) {
    return (ctr > 1) ? word + ending : word;
    //make_plural 函数用于根据单词出现的次数来决定是否加上复数形式的后缀 "s"。
    //这个函数的作用是根据给定的整数 ctr 和单词 word，
    //如果 ctr 不为1，则在 word 后面加上 "s" 后缀，表示复数形式。
    //否则，返回单数形式的单词。
}

ostream &operator<<(ostream &os, const QueryResult &qr){
    //如果找到单词，打印出现次数和所有出现的位置
    os<<qr._sought<<" occurs "<<qr._lines->size()<<" "
      <<make_plural(qr._lines->size(), "time", "s")<<endl;  
      //<<make_plural((*qr._lines).size(), "time", "s")<<endl;  这么写也对
      //在C++中，智能指针的重载操作符 -> 已经被设计为自动解引用的，
      //因此可以直接使用 qr._lines->size() 而不需要显式地进行解引用操作 *qr._lines
      //智能指针的 -> 操作符会自动解引用指针并调用相应的成员函数或访问成员变量。

    //打印单词出现的每一行
    //如果找不到该单词，则qr._lines里面没有内容，该循环不会打印出任何东西
    //注意qr._lines、qr._file都是share_ptr，要对它们解引用才能得到set、复制文本file
    size_t history;
    for(auto &num: *qr._lines){
        if(num != history)
        {
            os<<"\t(line " << num + 1 <<") "
              << *(qr._file->begin() + num)<<endl; //迭代器要解引用
        }
        history = num;
    }

    return os;
}

void runQueries(ifstream &infile){
    TextQuery tq(infile);
    while(true){
        cout<<"enter word to look for, or q to quit: ";
        string s;
        if(!(cin >> s) || s=="q")
            break;
        cout<<tq.query(s)<<endl;
    }
}

void test(){
    ifstream ifs("china_daily.txt");
    runQueries(ifs);
}

int main()
{   
    test();
    return 0;
}

